package silk

import (
	"math"

	"github.com/lostromb/concentus/go/comm/arrayUtil"
)

func silk_decode_core(
	psDec *SilkChannelDecoder,
	psDecCtrl *SilkDecoderControl,
	xq []int16,
	xq_ptr int,
	pulses []int16,
) {
	var i, k, lag, start_idx, sLTP_buf_idx, NLSF_interpolation_flag, signalType int
	var A_Q12 []int16
	B_Q14 := psDecCtrl.LTPCoef_Q14
	var B_Q14_ptr int
	var pxq int
	var sLTP []int16
	var sLTP_Q15 []int
	var LTP_pred_Q13, LPC_pred_Q10, Gain_Q10, inv_gain_Q31, gain_adj_Q16, rand_seed, offset_Q10 int
	var pred_lag_ptr int
	var pexc_Q14 int
	var pres_Q14 []int
	var pres_Q14_ptr int
	var res_Q14 []int
	var sLPC_Q14 []int

	inlines.OpusAssert(psDec.Prev_gain_Q16 != 0)

	sLTP = make([]int16, psDec.ltp_mem_length)
	sLTP_Q15 = make([]int, psDec.ltp_mem_length+psDec.Frame_length)
	res_Q14 = make([]int, psDec.subfr_length)
	sLPC_Q14 = make([]int, psDec.subfr_length+SilkConstants.MAX_LPC_ORDER)

	offset_Q10 = int(silk_Quantization_Offsets_Q10[psDec.Indices.SignalType>>1][psDec.Indices.QuantOffsetType])

	if psDec.Indices.NLSFInterpCoef_Q2 < 1<<2 {
		NLSF_interpolation_flag = 1
	} else {
		NLSF_interpolation_flag = 0
	}

	/* Decode excitation */
	rand_seed = int(psDec.Indices.Seed)
	for i = 0; i < psDec.Frame_length; i++ {
		rand_seed = inlines.Silk_RAND(rand_seed)
		psDec.exc_Q14[i] = inlines.Silk_LSHIFT(int(pulses[i]), 14)
		if psDec.exc_Q14[i] > 0 {
			psDec.exc_Q14[i] -= SilkConstants.QUANT_LEVEL_ADJUST_Q10 << 4
		} else {
			if psDec.exc_Q14[i] < 0 {
				psDec.exc_Q14[i] += SilkConstants.QUANT_LEVEL_ADJUST_Q10 << 4
			}
		}
		psDec.exc_Q14[i] += offset_Q10 << 4
		if rand_seed < 0 {
			psDec.exc_Q14[i] = -psDec.exc_Q14[i]
		}

		rand_seed = int(inlines.Silk_ADD32_ovflw(int32(rand_seed), int32(pulses[i])))
	}

	/* Copy LPC state */
	//	System.arraycopy(psDec.sLPC_Q14_buf, 0, sLPC_Q14, 0, SilkConstants.MAX_LPC_ORDER)
	copy(sLPC_Q14[0:], psDec.SLPC_Q14_buf[0:SilkConstants.MAX_LPC_ORDER])
	pexc_Q14 = 0
	pxq = xq_ptr
	sLTP_buf_idx = psDec.ltp_mem_length
	/* Loop over subframes */
	for k = 0; k < psDec.Nb_subfr; k++ {
		pres_Q14 = res_Q14
		pres_Q14_ptr = 0
		A_Q12 = psDecCtrl.PredCoef_Q12[k>>1]
		B_Q14_ptr = k * SilkConstants.LTP_ORDER
		signalType = int(psDec.Indices.SignalType)

		Gain_Q10 = inlines.Silk_RSHIFT(psDecCtrl.Gains_Q16[k], 6)
		inv_gain_Q31 = inlines.Silk_INVERSE32_varQ(psDecCtrl.Gains_Q16[k], 47)

		/* Calculate gain adjustment factor */
		if psDecCtrl.Gains_Q16[k] != psDec.Prev_gain_Q16 {
			gain_adj_Q16 = inlines.Silk_DIV32_varQ(psDec.Prev_gain_Q16, psDecCtrl.Gains_Q16[k], 16)

			/* Scale short term state */
			for i = 0; i < SilkConstants.MAX_LPC_ORDER; i++ {
				sLPC_Q14[i] = inlines.Silk_SMULWW(gain_adj_Q16, sLPC_Q14[i])
			}
		} else {
			gain_adj_Q16 = 1 << 16
		}

		/* Save inv_gain */
		inlines.OpusAssert(inv_gain_Q31 != 0)
		psDec.Prev_gain_Q16 = psDecCtrl.Gains_Q16[k]

		/* Avoid abrupt transition from voiced PLC to unvoiced normal decoding */
		if psDec.lossCnt != 0 && psDec.PrevSignalType == SilkConstants.TYPE_VOICED &&
			int(psDec.Indices.SignalType) != SilkConstants.TYPE_VOICED && k < SilkConstants.MAX_NB_SUBFR/2 {

			arrayUtil.MemSetWithOffset(B_Q14, 0, B_Q14_ptr, SilkConstants.LTP_ORDER)
			B_Q14[B_Q14_ptr+(SilkConstants.LTP_ORDER/2)] = int16(math.Trunc((0.25)*float64(int64(1)<<(14)) + 0.5))

			signalType = SilkConstants.TYPE_VOICED
			psDecCtrl.pitchL[k] = psDec.LagPrev
		}

		if signalType == SilkConstants.TYPE_VOICED {
			/* Voiced */
			lag = psDecCtrl.pitchL[k]

			/* Re-whitening */
			if k == 0 || (k == 2 && (NLSF_interpolation_flag != 0)) {
				/* Rewhiten with new A coefs */
				start_idx = psDec.ltp_mem_length - lag - psDec.LPC_order - SilkConstants.LTP_ORDER/2
				inlines.OpusAssert(start_idx > 0)

				if k == 2 {
					//	System.arraycopy(xq, xq_ptr, psDec.outBuf, psDec.ltp_mem_length, 2*psDec.subfr_length)
					copy(psDec.OutBuf[psDec.ltp_mem_length:], xq[xq_ptr:xq_ptr+2*psDec.subfr_length])
				}

				silk_LPC_analysis_filter(sLTP, start_idx, psDec.OutBuf, (start_idx + k*psDec.subfr_length),
					A_Q12, 0, psDec.ltp_mem_length-start_idx, psDec.LPC_order)

				/* After rewhitening the LTP state is unscaled */
				if k == 0 {
					/* Do LTP downscaling to reduce inter-packet dependency */
					inv_gain_Q31 = inlines.Silk_LSHIFT(inlines.Silk_SMULWB(inv_gain_Q31, psDecCtrl.LTP_scale_Q14), 2)
				}
				for i = 0; i < lag+SilkConstants.LTP_ORDER/2; i++ {
					sLTP_Q15[sLTP_buf_idx-i-1] = inlines.Silk_SMULWB(inv_gain_Q31, int(sLTP[psDec.ltp_mem_length-i-1]))
				}
			} else /* Update LTP state when Gain changes */ if gain_adj_Q16 != int(int32(1)<<16) {
				for i = 0; i < lag+SilkConstants.LTP_ORDER/2; i++ {
					sLTP_Q15[sLTP_buf_idx-i-1] = inlines.Silk_SMULWW(gain_adj_Q16, sLTP_Q15[sLTP_buf_idx-i-1])
				}
			}
		}

		/* Long-term prediction */
		if signalType == SilkConstants.TYPE_VOICED {
			/* Set up pointer */
			pred_lag_ptr = sLTP_buf_idx - lag + SilkConstants.LTP_ORDER/2
			for i = 0; i < psDec.subfr_length; i++ {
				/* Unrolled loop */
				/* Avoids introducing a bias because inlines.Silk_SMLAWB() always rounds to -inf */
				LTP_pred_Q13 = 2
				LTP_pred_Q13 = inlines.Silk_SMLAWB(LTP_pred_Q13, sLTP_Q15[pred_lag_ptr], int(B_Q14[B_Q14_ptr]))
				LTP_pred_Q13 = inlines.Silk_SMLAWB(LTP_pred_Q13, sLTP_Q15[pred_lag_ptr-1], int(B_Q14[B_Q14_ptr+1]))
				LTP_pred_Q13 = inlines.Silk_SMLAWB(LTP_pred_Q13, sLTP_Q15[pred_lag_ptr-2], int(B_Q14[B_Q14_ptr+2]))
				LTP_pred_Q13 = inlines.Silk_SMLAWB(LTP_pred_Q13, sLTP_Q15[pred_lag_ptr-3], int(B_Q14[B_Q14_ptr+3]))
				LTP_pred_Q13 = inlines.Silk_SMLAWB(LTP_pred_Q13, sLTP_Q15[pred_lag_ptr-4], int(B_Q14[B_Q14_ptr+4]))
				pred_lag_ptr += 1

				/* Generate LPC excitation */
				pres_Q14[pres_Q14_ptr+i] = inlines.Silk_ADD_LSHIFT32(psDec.exc_Q14[pexc_Q14+i], LTP_pred_Q13, 1)

				/* Update states */
				sLTP_Q15[sLTP_buf_idx] = inlines.Silk_LSHIFT(pres_Q14[pres_Q14_ptr+i], 1)
				sLTP_buf_idx++
			}
		} else {
			pres_Q14 = psDec.exc_Q14
			pres_Q14_ptr = pexc_Q14
		}

		for i = 0; i < psDec.subfr_length; i++ {
			/* Short-term prediction */
			inlines.OpusAssert(psDec.LPC_order == 10 || psDec.LPC_order == 16)
			/* Avoids introducing a bias because inlines.Silk_SMLAWB() always rounds to -inf */
			LPC_pred_Q10 = inlines.Silk_RSHIFT(psDec.LPC_order, 1)
			LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-1], int(A_Q12[0]))
			LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-2], int(A_Q12[1]))
			LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-3], int(A_Q12[2]))
			LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-4], int(A_Q12[3]))
			LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-5], int(A_Q12[4]))
			LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-6], int(A_Q12[5]))
			LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-7], int(A_Q12[6]))
			LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-8], int(A_Q12[7]))
			LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-9], int(A_Q12[8]))
			LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-10], int(A_Q12[9]))
			if psDec.LPC_order == 16 {
				LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-11], int(A_Q12[10]))
				LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-12], int(A_Q12[11]))
				LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-13], int(A_Q12[12]))
				LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-14], int(A_Q12[13]))
				LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-15], int(A_Q12[14]))
				LPC_pred_Q10 = inlines.Silk_SMLAWB(LPC_pred_Q10, sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i-16], int(A_Q12[15]))
			}

			/* Add prediction to LPC excitation */
			sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i] = inlines.Silk_ADD_LSHIFT32(pres_Q14[pres_Q14_ptr+i], LPC_pred_Q10, 4)

			/* Scale with gain */
			xq[pxq+i] = int16(inlines.Silk_SAT16(inlines.Silk_RSHIFT_ROUND(inlines.Silk_SMULWW(sLPC_Q14[SilkConstants.MAX_LPC_ORDER+i], Gain_Q10), 8)))
		}

		/* DEBUG_STORE_DATA( dec.pcm, pxq, psDec.subfr_length * sizeof( short ) ) */

		/* Update LPC filter state */
		//System.arraycopy(sLPC_Q14, psDec.subfr_length, sLPC_Q14, 0, SilkConstants.MAX_LPC_ORDER)
		copy(sLPC_Q14, sLPC_Q14[psDec.subfr_length:psDec.subfr_length+SilkConstants.MAX_LPC_ORDER])
		pexc_Q14 += psDec.subfr_length
		pxq += psDec.subfr_length
	}

	/* Save LPC state */
	//	System.arraycopy(sLPC_Q14, 0, psDec.sLPC_Q14_buf, 0, SilkConstants.MAX_LPC_ORDER)
	copy(psDec.SLPC_Q14_buf, sLPC_Q14[:SilkConstants.MAX_LPC_ORDER])

}
